package net.zxjava.gateway.route;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.cloud.netflix.zuul.util.RequestUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;

import net.zxjava.gateway.service.RouteService;

/**
 * 平台动态路由配置
 *
 */
public class ZxjavaRouteLocator implements RouteLocator {

	private final static Logger logger = LoggerFactory.getLogger(ZxjavaRouteLocator.class);

	private ZuulProperties properties;

	private PathMatcher pathMatcher = new AntPathMatcher();

	private String dispatcherServletPath = "/";

	private String zuulServletPath;

	private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();

	public static final String PREFIX = "/zxjava-gateway";

	public static final String PLATFORM = "ZXJAVA-PROVIDER";

	@Resource
	private RouteService routeService;

	public ZxjavaRouteLocator(String servletPath, ZuulProperties properties) {
		this.properties = properties;
		if (servletPath != null && StringUtils.hasText(servletPath)) {
			this.dispatcherServletPath = servletPath;
		}

		this.zuulServletPath = properties.getServletPath();
	}

	@Override
	public List<Route> getRoutes() {
		if (this.routes.get() == null) {
			this.routes.set(locateRoutes(null));
		}
		List<Route> values = new ArrayList<>();
		for (String url : this.routes.get().keySet()) {
			ZuulRoute route = this.routes.get().get(url);
			String path = route.getPath();
			values.add(getRoute(route, path));
		}
		return values;
	}

	@Override
	public Collection<String> getIgnoredPaths() {
		return this.properties.getIgnoredPatterns();
	}

	@Override
	public Route getMatchingRoute(final String originPath) {
		logger.info(originPath);
		ZuulRoute route = null;

		if (!StringUtils.startsWithIgnoreCase(originPath, PREFIX)) {
			for (Entry<String, ZuulRoute> entry : this.routes.get().entrySet()) {
				ZuulRoute zuulRoute = entry.getValue();
				if (PLATFORM.equals((zuulRoute.getServiceId()))) {
					route = zuulRoute;
				}
			}
			return getRoute(route, originPath);
		}

		String path = originPath.substring(PREFIX.length(), originPath.length());

		if (this.routes.get() == null) {
			this.routes.set(locateRoutes(null));
		}

		String adjustedPath = adjustPath(path);

		if (!matchesIgnoredPatterns(adjustedPath) && route == null) {
			for (Entry<String, ZuulRoute> entry : this.routes.get().entrySet()) {
				String pattern = entry.getKey();
				if (this.pathMatcher.match(pattern, adjustedPath)) {
					route = entry.getValue();
					if (PLATFORM.equals((route.getServiceId()))) {
						route = null;
						continue;
					} else {
						break;
					}
				}
			}
		}
		return getRoute(route, adjustedPath);
	}

	private Route getRoute(ZuulRoute route, String path) {
		if (route == null) {
			return null;
		}
		String targetPath = path;
		String prefix = this.properties.getPrefix();
		if (path.startsWith(prefix) && this.properties.isStripPrefix()) {
			targetPath = path.substring(prefix.length());
		}
		if (route.isStripPrefix()) {
			int index = route.getPath().indexOf("*") - 1;
			if (index > 0) {
				String routePrefix = route.getPath().substring(0, index);
				targetPath = targetPath.replaceFirst(routePrefix, "");
				prefix = prefix + routePrefix;
			}
		}
		Boolean retryable = this.properties.getRetryable();
		if (route.getRetryable() != null) {
			retryable = route.getRetryable();
		}
		return new Route(route.getId(), targetPath, route.getLocation(), prefix, retryable,
				route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null);
	}

	public void doRefresh(String serverName) {
		this.routes.set(locateRoutes(serverName));
	}

	protected Map<String, ZuulRoute> locateRoutes(String serverName) {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();

		List<ZuulRoute> routes = routeService.getZuulRoutes(serverName);
		for (ZuulRoute route : routes) {
			routesMap.put(route.getPath(), route);
		}

		return routesMap;
	}

	protected boolean matchesIgnoredPatterns(String path) {
		for (String pattern : this.properties.getIgnoredPatterns()) {
			if (this.pathMatcher.match(pattern, path)) {
				return true;
			}
		}
		return false;
	}

	private String adjustPath(final String path) {
		String adjustedPath = path;

		if (RequestUtils.isDispatcherServletRequest() && StringUtils.hasText(this.dispatcherServletPath)) {
			if (!this.dispatcherServletPath.equals("/")) {
				adjustedPath = path.substring(this.dispatcherServletPath.length());
			}
		} else if (RequestUtils.isZuulServletRequest()) {
			if (StringUtils.hasText(this.zuulServletPath) && !this.zuulServletPath.equals("/")) {
				adjustedPath = path.substring(this.zuulServletPath.length());
			}
		} else {

		}

		return adjustedPath;
	}
}
